/*
 * Copyright 2002-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.aop.framework;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.AopInvocationException;
import org.springframework.aop.RawTargetAccess;
import org.springframework.aop.TargetSource;
import org.springframework.aop.support.AopUtils;
import org.springframework.core.DecoratingProxy;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

/**
 * spring的aop框架 基于 jdk的动态代理 的 实现
 * <p>
 * 创建jdk的动态代理。实现AopProxy 公开的接口，动态代理不能用于代理类中定义的方法，而不是 接口中定义的方法。
 * 什么意思？就是你在接口定义了一个方法，他是可以被jdk的动态代理代理的，但是你在接口中没有定义，而是仅仅在实现类定义了
 * 这个时候，是不能够被代理的。
 */
final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {

	private static final long serialVersionUID = 5531744639992436476L;


	/**
	 *
	 *
	 * 注意：我们可以避免这个类和cglib之间的代码重复的，具体怎么做呢？
	 * 通过将调用重构为模板方法来代理。但是这种方式与复制粘贴解决方案相比较，增加了至少百分之十的性能开销。
	 * 因此我们牺牲哭了优雅的实现方式。
	 * 目前这种写法，我们可以更容易的在每一个类中做一些小的优化，而不必考虑兼容性。
	 *
	 */


	private static final Log logger = LogFactory.getLog(JdkDynamicAopProxy.class);

	/** 代理的配置 */
	private final AdvisedSupport advised;

	private final Class<?>[] proxiedInterfaces;

	/**
	 * Is the {@link #equals} method defined on the proxied interfaces?
	 */
	private boolean equalsDefined;

	/**
	 * Is the {@link #hashCode} method defined on the proxied interfaces?
	 */
	private boolean hashCodeDefined;


	/**
	 * 使用给定的配置，通过构造器创建aop 的动jdk态代理对象
	 * 这里的config是啥？就是我们的代理工厂对象
	 */
	public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
		//非空断言
		Assert.notNull(config, "AdvisedSupport must not be null");
		//如果配置里面的切面数==0 && 配置里面的目标对象是空对象，那么代理无法继续往下走了，直接抛异常中断
		if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
			throw new AopConfigException("No advisors and no TargetSource specified");
		}
		this.advised = config;
		//获取当前被代理对象实现的接口数组 ,具体的实现逻辑？
		this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
		//查找所有被代理的接口，如果有equals 和 hashcode就打个标
		findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
	}


	@Override
	public Object getProxy() {
		//这里如果没有传类加载器，就使用默认的类加载器，默认是线程上下文类加载器
		return getProxy(ClassUtils.getDefaultClassLoader());
	}

	@Override
	public Object getProxy(@Nullable ClassLoader classLoader) {
		//打印日志的逻辑
		if (logger.isTraceEnabled()) {
			logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
		}
		//通过jdk的动态代理来创建代理对象 this == this::invoke  该方法最终会返回一个代理类对象
		return Proxy.newProxyInstance(classLoader, this.proxiedInterfaces, this);
	}


	private void findDefinedEqualsAndHashCodeMethods(Class<?>[] proxiedInterfaces) {
		for (Class<?> proxiedInterface : proxiedInterfaces) {
			//获取接口里面的所有方法
			Method[] methods = proxiedInterface.getDeclaredMethods();
			for (Method method : methods) {
				//如果是equals方法，打个标
				if (AopUtils.isEqualsMethod(method)) {
					this.equalsDefined = true;
				}
				//如果是hashcode方法，打个标
				if (AopUtils.isHashCodeMethod(method)) {
					this.hashCodeDefined = true;
				}
				//如果已经标记过了，直接返回
				if (this.equalsDefined && this.hashCodeDefined) {
					return;
				}
			}
		}
	}


	@Override
	@Nullable
	public Object invoke(Object proxy/*代理对象*/, Method method/*目标方法*/, Object[] args/*目标方法对应的参数*/) throws Throwable {
		Object oldProxy = null;
		boolean setProxyContext = false;
		//advised 这里实际上就是proxyFactory的引用，targetSource 实际上就是上层传递封装的targetsource
		TargetSource targetSource = this.advised.targetSource;
		Object target = null;

		try {
			/*如果代理类实现的接口里面有equals方法，就使用里面的，否则使用jdk提供的equals方法*/
			if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
				return equals(args[0]);
			}
			//如果代理类实现的接口里面提供了hashcode方法，就是用里面的，否则用jdk的
			else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
				return hashCode();
			}
			/*暂时尚未用到，TODO*/
			else if (method.getDeclaringClass() == DecoratingProxy.class) {
				return AopProxyUtils.ultimateTargetClass(this.advised);
			} else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
					method.getDeclaringClass().isAssignableFrom(Advised.class)) {
				return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
			}
			//返回值
			Object retVal;
			/**
			 * 是否需要将当前的代理对象设置在aop上下文中
			 * aop上下文对象实际上就是一个threadLocal
			 * 为什么要引入一个aop上下文？
			 * 目标对象A B
			 * 通过代理的方式调用A.eat()
			 * 这个eat方法里面有恰恰调用到了B的方法，这个时候B对象实际上并不是代理对象，所以
			 * b的方法执行前后并不会被增强，为了解决这个问题，就引入了aop的上下文
			 */
			if (this.advised.exposeProxy) {
				// 将当前代理对象设置到aop上下文中，并返回老的代理对象
				oldProxy = AopContext.setCurrentProxy(proxy);
				setProxyContext = true;
			}

			/*根据targetSource拿到目标对象*/
			target = targetSource.getTarget();
			/*根据目标对象拿到目标对象的类型*/
			Class<?> targetClass = (target != null ? target.getClass() : null);

			// 这里是最关键的地方，查找适合该方法的增强  具体是如何查找的？
			List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

			/*查询出匹配当前方法拦截器的数量是0 说明当前方法不需要被增强，直接通过反射调用目标对象的目标方法。*/
			if (chain.isEmpty()) {

				Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
				/*调用目标对象的目标方法*/
				retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
			}
			else {
				/*说明有匹配当前method的方法拦截器，说明要做增强处理 */
				MethodInvocation invocation =
						new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
				/*核心驱动逻辑在ReflectiveMethodInvocation*/
				retVal = invocation.proceed();
			}

			/*获取方法的返回值类型*/
			Class<?> returnType = method.getReturnType();
			/*如果目标方法返回目标对象 ，做一个替换  ，返回代理对象*/
			if (retVal != null && retVal == target &&
					returnType != Object.class && returnType.isInstance(proxy) &&
					!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
				retVal = proxy;
			}
			//方法是void类型，但是返回值类型还不为空，说明有问题，抛异常
			else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
				throw new AopInvocationException(
						"Null return value from advice does not match primitive return type for: " + method);
			}
			return retVal;
		}
		finally {
			if (target != null && !targetSource.isStatic()) {
				// Must have come from TargetSource.
				targetSource.releaseTarget(target);
			}
			if (setProxyContext) {
				// 将上次设置的proxy在此设置回去到aop上下文内
				//因为当前代理对象的目标方法已经完成了，需要回到上一层逻辑
				//属于恢复现场的逻辑
				AopContext.setCurrentProxy(oldProxy);
			}
		}
	}


	/**
	 * Equality means interfaces, advisors and TargetSource are equal.
	 * <p>The compared object may be a JdkDynamicAopProxy instance itself
	 * or a dynamic proxy wrapping a JdkDynamicAopProxy instance.
	 */
	@Override
	public boolean equals(@Nullable Object other) {
		if (other == this) {
			return true;
		}
		if (other == null) {
			return false;
		}

		JdkDynamicAopProxy otherProxy;
		if (other instanceof JdkDynamicAopProxy) {
			otherProxy = (JdkDynamicAopProxy) other;
		}
		else if (Proxy.isProxyClass(other.getClass())) {
			InvocationHandler ih = Proxy.getInvocationHandler(other);
			if (!(ih instanceof JdkDynamicAopProxy)) {
				return false;
			}
			otherProxy = (JdkDynamicAopProxy) ih;
		}
		else {
			// Not a valid comparison...
			return false;
		}

		// If we get here, otherProxy is the other AopProxy.
		return AopProxyUtils.equalsInProxy(this.advised, otherProxy.advised);
	}

	/**
	 * Proxy uses the hash code of the TargetSource.
	 */
	@Override
	public int hashCode() {
		return JdkDynamicAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode();
	}

}
